# -*- coding: utf-8 -*-
#
# Copyright 2009 Vanderbilt University
# 
# Licensed under the Apache License, Version 2.0 (the "License"); 
# you may not use this file except in compliance with the License. 
# You may obtain a copy of the License at 
# 
#     http://www.apache.org/licenses/LICENSE-2.0 
# 
# Unless required by applicable law or agreed to in writing, software 
# distributed under the License is distributed on an "AS IS" BASIS, 
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
# See the License for the specific language governing permissions and 
# limitations under the License. 

"""
The :mod:`hornet.loading` module provides utilities for the loading of classes
and objects dynamically.  Most of these tools are used to assist in the loading
plugins.

.. moduleauthor:: John Paulett <john.paulett -at- vanderbilt.edu>
"""

import os.path
import sys

def load(module, name):
    """Loads a function or class specified by name from a module.
    
    >>> f = load('hornet.loading', 'load')
    >>> f.__module__
    'hornet.loading'
    >>> f.__name__
    'load'
    """
    __import__(module)
    mod = sys.modules[module]
    return getattr(mod, name)

def find_instances(module, cls):
    """Finds the instance of *cls* in the given *module*.
    
    >>> instance = find_instance('module', Class) #doctest: +SKIP
    """
    m = full_import(module)
    return [v for v in m.__dict__.itervalues() if isinstance(v, cls)]

def load_class(filename, cls):
    """Wrapper for :func:`hornet.loading.find_class' that finds the *cls* in
    a file that is not necessarily on sys.path, specified by *filename*.
    
    >>> instance = load_class('../config.py', Class) #doctest: +SKIP
    """
    directory, module, ext = path_components(filename)
   
    # temporarily add the directory to the top of the path so it can be found
    # first
    sys.path.insert(0, directory)
    
    try:
        instance = find_instances(module, cls)[0]
    except IndexError:
        raise ImportError, 'Could not import %s from %s' % (cls.__name__, 
                                                            filename)
    # clean up the path
    del sys.path[0]
    
    return instance

def full_import(name):
    """Returns the modules specified by *name*. Workaround to import 
    sub-modules, as described in the Python builtin_ docs.
    
    .. _builtin: http://www.python.org/doc/2.5.2/lib/built-in-funcs.html
    """
    module = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        module = getattr(module, comp)
    return module


def path_components(filename):
    """Takes a path (relative or absolute) to a file, specified by *filename*, 
    and returns the full path directory, the name with the extension, and the 
    file extension (including a period), as a tuple.
    
    >>> dir, mod, ext = path_components('loading.py') #doctest: +SKIP
    ('/home/hornet/', 'loading', '.py')
    """
    abspath = os.path.abspath(filename)
    if not os.path.isfile(abspath):
        raise IOError, 'Could not import file: %s' % abspath
    dir, fn = os.path.split(abspath)
    module, ext = os.path.splitext(fn) 
    return dir, module, ext
